//
// Created by Tommy on 15/5/19.
// Copyright (c) 2015 com.koala. All rights reserved.
//

#import "KLIoC.h"
#import <objc/runtime.h>


static char *get_prop_type(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    const char *start;
    int len = strlen(attributes);

    // Ok, let's get over it
    if (len >= 3 && attributes[0] == 'T' && attributes[1] == '@' && attributes[2] == '"') {
        // Find the ','
        start = attributes + 3;
        char *end = strchr(start, '"');

        if (end) {
            char *type = malloc((end - start) * sizeof(char) + 1);
            strncpy(type, start, end - start);
            type[end - start] = '\0';

            return type;
        }
        else {
            return "";
        }
    }
    else {
        return "";
    }
}

@implementation KLIoC {
    NSMutableDictionary *_dependencies;
    BOOL _initialized;
}

- (id)init
{
    self = [super init];

    if (self) {
        _dependencies = [[NSMutableDictionary alloc] initWithCapacity:5];
        [_dependencies setObject:self forKey:@"serviceBox"];
        _initialized = NO;
    }

    return self;
}

- (void)registerDependency:(NSObject *)dependency as:(NSString *)name
{
    [_dependencies setObject:dependency forKey:name];
}

- (BOOL)propertyType:(NSString *)propType matchesWithDependency:(NSObject *)dependency
{
    if ([propType characterAtIndex:0] == '<') {
        // Matching against a protocol

        NSString *protocolName = [propType substringWithRange:NSMakeRange(1, propType.length - 2)];
        Protocol *protocol = NSProtocolFromString(protocolName);
        return [dependency.class conformsToProtocol:protocol];
    }
    else {
        // Matching against a class
        Class propertyClass = NSClassFromString(propType);
        return [dependency isKindOfClass:propertyClass];
    }
}

- (void)injectInto:(NSObject *)target forClass:(Class)class
{
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(class, &outCount);
    for(i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);

        if(propName) {
            // Lookup the dependency
            NSString *propertyName = [NSString stringWithCString:propName encoding:NSUTF8StringEncoding];
            NSObject *dependency = [_dependencies objectForKey:propertyName];

            if (dependency != nil) {
                char *propType = get_prop_type(property);
                NSString *propertyType = [NSString stringWithCString:propType encoding:NSUTF8StringEncoding];
                if ([self propertyType:propertyType matchesWithDependency:dependency]) {
                    [target setValue:dependency forKey:propertyName];
                }
            }
        }
    }
    free(properties);
}

- (int)fill:(NSObject *)target
{
    [self initialize];

    Class class = target.class;
    do {
        [self injectInto:target forClass:class];
        class = class_getSuperclass(class);
    } while (class != nil);

    return 0;
}

- (void)initialize
{
    @synchronized(self) {
        if (_initialized) {
            return;
        }
        else {
            _initialized = YES;
        }
    }

    NSEnumerator *enumerator = [_dependencies objectEnumerator];
    // Inject the dependencies into the other ones
    for (NSObject *dependency in enumerator) {
        [self fill:dependency];
    }
}

+ (KLIoC *)defaultIoC
{
    static KLIoC *box = nil;

    @synchronized([KLIoC class]) {
        if (box == nil) {
            box = [[KLIoC alloc] init];
        }
    }

    return box;
}

@end